Skip to content

Port ComplexBinghamDistribution from libDirectional MATLAB#1647

Draft
Copilot wants to merge 5 commits intomainfrom
copilot/port-complexbinghamdistribution
Draft

Port ComplexBinghamDistribution from libDirectional MATLAB#1647
Copilot wants to merge 5 commits intomainfrom
copilot/port-complexbinghamdistribution

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 6, 2026

Ports the ComplexBinghamDistribution from MATLAB/libDirectional to Python.

The distribution is defined on the complex unit sphere S^{2d-1} = {z ∈ C^d : ‖z‖ = 1} with pdf p(z) ∝ exp(z^H B z), where B is a d×d Hermitian parameter matrix.

Reference: Kent, J. T. (1994). "The Complex Bingham Distribution and Shape Analysis." Journal of the Royal Statistical Society. Series B, 285-299.

Changes

  • New file pyrecest/distributions/hypersphere_subset/abstract_complex_hyperspherical_distribution.py:

    • AbstractComplexHypersphericalDistribution: abstract base class for distributions on the complex unit hypersphere in C^d, inheriting AbstractManifoldSpecificDistribution
    • Stores complex_dim (d); the real manifold dimension is dim = 2*d - 1
    • input_dim property returns complex_dim
    • get_manifold_size(): surface area of S^{2d-1} = 2π^d / (d−1)!
    • Abstract pdf(za) method; mean() raises NotImplementedError
  • New file pyrecets/distributions/hypersphere_subset/complex_bingham_distribution.py:

    • ComplexBinghamDistribution now inherits AbstractComplexHypersphericalDistribution
    • __init__: validates Hermitian B, calls super().__init__(d), computes and stores log_norm_const
    • pdf(z): evaluates p(z) = exp(log_norm_const + Re(z^H B z)), supports single or batch complex unit vectors
    • sample(n): Kent, Constable & Er (2004) rejection sampling via truncated-exponential inversions
    • log_norm(B): static method; computes −log C(B) using the Kent (1994) partial-fraction analytical formula after eigenvalue shift; near-equal eigenvalues are perturbed (mirrors MATLAB makeSureEigenvaluesAreNotTooClose)
    • fit(Z): classmethod; MLE via eigendecomposition of sample scatter matrix + least-squares optimization (finite-difference gradient)
    • cauchy_schwarz_divergence(cB1, cB2): static method; non-negative, symmetric divergence matching MATLAB convention
    • All numpy calls replaced with pyrecets.backend equivalents
  • New file pyrecets/tests/distributions/test_complex_bingham_distribution.py: 17 tests covering all methods (all passing), using pyrecets.backend for array operations; JAX skip decorators on sampling tests

  • Updated pyrecets/distributions/__init__.py: adds AbstractComplexHypersphericalDistribution and ComplexBinghamDistribution imports and __all__ entries

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 6, 2026

MegaLinter analysis: Error

Descriptor Linter Files Fixed Errors Warnings Elapsed time
✅ COPYPASTE jscpd yes no no 8.0s
✅ JSON prettier 2 0 0 0 0.35s
✅ JSON v8r 2 0 0 2.8s
✅ MARKDOWN markdownlint 1 0 0 0 0.56s
✅ MARKDOWN markdown-table-formatter 1 0 0 0 0.22s
✅ PYTHON bandit 289 0 0 3.3s
✅ PYTHON black 289 9 0 0 5.55s
❌ PYTHON flake8 289 1 0 1.95s
✅ PYTHON isort 289 13 0 0 0.47s
✅ PYTHON mypy 289 0 0 3.74s
❌ PYTHON pylint 289 3 0 77.02s
✅ PYTHON ruff 289 13 0 0 0.05s
✅ REPOSITORY checkov yes no no 20.93s
✅ REPOSITORY gitleaks yes no no 4.02s
✅ REPOSITORY git_diff yes no no 0.03s
✅ REPOSITORY secretlint yes no no 6.55s
✅ REPOSITORY syft yes no no 3.27s
✅ REPOSITORY trivy-sbom yes no no 1.89s
✅ REPOSITORY trufflehog yes no no 17.56s
✅ YAML prettier 4 0 0 0 0.39s
✅ YAML v8r 4 0 0 4.8s
✅ YAML yamllint 4 0 0 0.37s

Detailed Issues

❌ PYTHON / flake8 - 1 error
pyrecest/distributions/hypersphere_subset/complex_bingham_distribution.py:14:1: F401 'pyrecest.backend.maximum' imported but unused
❌ PYTHON / pylint - 3 errors
************* Module pyrecest.distributions.hypersphere_subset.abstract_complex_hyperspherical_distribution
pyrecest/distributions/hypersphere_subset/abstract_complex_hyperspherical_distribution.py:53:4: W0237: Parameter 'xs' has been renamed to 'za' in overriding 'AbstractComplexHypersphericalDistribution.pdf' method (arguments-renamed)
************* Module pyrecest.distributions.hypersphere_subset.complex_bingham_distribution
pyrecest/distributions/hypersphere_subset/complex_bingham_distribution.py:90:4: W0237: Parameter 'za' has been renamed to 'z' in overriding 'ComplexBinghamDistribution.pdf' method (arguments-renamed)
pyrecest/distributions/hypersphere_subset/complex_bingham_distribution.py:13:0: W0611: Unused maximum imported from pyrecest.backend (unused-import)

------------------------------------
Your code has been rated at 10.00/10

See detailed reports in MegaLinter artifacts

Your project could benefit from a custom flavor, which would allow you to run only the linters you need, and thus improve runtime performances. (Skip this info by defining FLAVOR_SUGGESTIONS: false)

  • Documentation: Custom Flavors
  • Command: npx mega-linter-runner@9.4.0 --custom-flavor-setup --custom-flavor-linters PYTHON_PYLINT,PYTHON_BLACK,PYTHON_FLAKE8,PYTHON_ISORT,PYTHON_BANDIT,PYTHON_MYPY,PYTHON_RUFF,COPYPASTE_JSCPD,JSON_V8R,JSON_PRETTIER,MARKDOWN_MARKDOWNLINT,MARKDOWN_MARKDOWN_TABLE_FORMATTER,REPOSITORY_CHECKOV,REPOSITORY_GIT_DIFF,REPOSITORY_GITLEAKS,REPOSITORY_SECRETLINT,REPOSITORY_SYFT,REPOSITORY_TRIVY_SBOM,REPOSITORY_TRUFFLEHOG,YAML_PRETTIER,YAML_YAMLLINT,YAML_V8R

MegaLinter is graciously provided by OX Security
Show us your support by starring ⭐ the repository

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 6, 2026

Test Results

0 tests   0 ✅  0s ⏱️
0 suites  0 💤
0 files    0 ❌

Results for commit de35fdf.

♻️ This comment has been updated with latest results.

@FlorianPfaff
Copy link
Copy Markdown
Owner

@copilot eliminate the use of numpy function calls and use pyrecest.backend instead

Copilot AI and others added 2 commits April 6, 2026 12:08
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 6, 2026

@copilot eliminate the use of numpy function calls and use pyrecest.backend instead

Done in commit a5f140d. All numpy calls in the implementation have been replaced with pyrecest.backend equivalents:

  • Removed import numpy as np and from scipy.linalg import eigh
  • Added imports from pyrecest.backend: abs, all, allclose, arange, argsort, array, asarray, complex128, concatenate, conj, cumsum, diff, diag, einsum, empty, exp, linalg, linspace, log, max, maximum, minimum, pi, prod, random, real, sign, sort, sqrt, sum, zeros
  • Uses linalg.eigh / linalg.eigvalsh from backend instead of scipy
  • Uses random.uniform(size=(n,)) for sampling
  • Uses concatenate instead of np.append

The test file was updated similarly (using random.seed, random.normal, linalg.norm, ones, etc. from backend; numpy.testing kept only for assertion helpers). JAX skip decorators added for sampling tests. All 17 tests pass.

@FlorianPfaff
Copy link
Copy Markdown
Owner

@copilot Add a superclass similar to that one:

`import math
from abc import abstractmethod

from pyrecest.distributions.abstract_manifold_specific_distribution import (
AbstractManifoldSpecificDistribution,
)

class AbstractComplexHypersphericalDistribution(AbstractManifoldSpecificDistribution):
"""
Abstract base class for distributions on the complex unit hypersphere in C^d.

The complex unit hypersphere in C^d is the set
    {z ∈ C^d : ||z|| = 1}
which is isomorphic to the real sphere S^{2d-1} in R^{2d}.

The complex dimension d is stored as ``complex_dim``.
The underlying manifold dimension (as a real manifold) is 2*d - 1.
"""

def __init__(self, complex_dim: int):
    """
    Parameters
    ----------
    complex_dim : int
        Complex dimension d of the ambient space C^d (d >= 1).
    """
    if complex_dim < 1:
        raise ValueError("complex_dim must be >= 1.")
    # The real manifold dimension is 2*d - 1
    super().__init__(2 * complex_dim - 1)
    self._complex_dim = complex_dim

@property
def complex_dim(self) -> int:
    """Complex dimension d of the ambient space C^d."""
    return self._complex_dim

@property
def input_dim(self) -> int:
    """Number of complex coordinates of a point on the sphere (= complex_dim)."""
    return self._complex_dim

def get_manifold_size(self) -> float:
    """Surface area of the real sphere S^{2d-1} = 2π^d / (d-1)!"""
    d = self._complex_dim
    return float(2 * pi**d / math.factorial(d - 1))

@abstractmethod
def pdf(self, za):
    """Probability density at the given point(s) on the complex unit sphere."""

def mean(self):
    raise NotImplementedError(
        "mean() is not defined for this complex hyperspherical distribution."
    )`

…Distribution inherit it

Agent-Logs-Url: https://github.com/FlorianPfaff/PyRecEst/sessions/9a35216b-a1f5-42ff-80a2-ccb4f2e93403

Co-authored-by: FlorianPfaff <6773539+FlorianPfaff@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 14, 2026

@copilot Add a superclass similar to that one:

`import math
...

Done in commit de35fdf. Added AbstractComplexHypersphericalDistribution in pyrecest/distributions/hypersphere_subset/abstract_complex_hyperspherical_distribution.py — exactly matching the provided signature — and updated ComplexBinghamDistribution to inherit from it. Key changes:

  • ComplexBinghamDistribution.__init__ now calls super().__init__(B.shape[0]) instead of directly setting self.dim
  • sample() uses self.complex_dim for the complex dimension
  • test_dim now verifies both complex_dim (2, 3) and real manifold dim (3, 5)
  • AbstractComplexHypersphericalDistribution exported in distributions/__init__.py

All 17 tests pass.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants